/**
 * Copyright (c) 2012, Hakans Pirates - 
 * Johan Andersson spaiki17@gmail.com, 
 * Mattias Lundberg lundberg.mattias@gmail.com, 
 * Samuel Sjodin sjoodin@gmail.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the <organization> nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *  
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package pirates.network;

import com.jme3.math.Vector3f;
import java.util.concurrent.Callable;

import pirates.Settings;
import pirates.Settings.*;
import pirates.network.messages.*;

import com.jme3.network.Client;
import com.jme3.network.ClientStateListener;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.scene.Spatial;

import pirates.Pirates;
import pirates.crates.FloatingCrate;
import pirates.network.messages.ChatMessage;
import pirates.notifications.Notify;
import pirates.ship.ShipControl;
import pirates.ship.cannon.CannonBall;

public class ClientNetListener implements MessageListener, ClientStateListener {

    private Client client;
    private Pirates app;

    public ClientNetListener(Client client, Pirates app) {
        this.client = client;
        this.app = app;
        this.client.addClientStateListener(this);
        this.client.addMessageListener(this);
    }

    @Override
    public void clientConnected(Client client) {
        // Notify.debug("connected to server. Sending hello message.");
        // client.send(new HelloMessage("Hi there!"));
        // client.send(new HandshakeMessage(
        // Settings.PROTOCOL_VERSION,
        // Settings.CLIENT_VERSION,
        // Settings.SERVER_VERSION));
    }

    @Override
    public void clientDisconnected(Client client, DisconnectInfo arg1) {
        Notify.popup("Disconnected from server");
        System.exit(0);
    }

    @SuppressWarnings("unchecked")
    @Override
    public void messageReceived(Object source, Message msg) {
        final Message messoge = msg;
        app.enqueue(new Callable() {

            Message msg = messoge;

            @Override
            public Object call() throws Exception {

                // Notify.debug("message received from server: "+msg);

                if (msg instanceof HelloMessage) {
                    HelloMessage message = (HelloMessage) msg;
                    Notify.debug("Server says " + message.getMessage());
                    if (!Settings.SENDING_NW_UPDATES) {
                        Notify.debug("connected to server. Sending hello message.");
                        client.send(new HandshakeMessage(
                                Settings.PROTOCOL_VERSION,
                                Settings.CLIENT_VERSION,
                                Settings.SERVER_VERSION));
                    }

                } else if (msg instanceof HandshakeMessage) {
                    // server has accepted the handshake. Try joining the game!
                    Notify.debug("Trying to join");
                    client.send(new ClientJoinMessage(Settings.HOST_NAME,
                            "nopassw"));

                } else if (msg instanceof ChatMessage) {
                    ChatMessage mess = (ChatMessage) msg;
                    app.getChat().appendMessage(mess.name, mess.text);

                } else if (msg instanceof CrateMessage) {
                    CrateMessage m = (CrateMessage) msg;

                    if (m.pickup) {
                        if (m.ship.equals(Settings.HOST_NAME)) {
                            return null;
                        }
                        app.getCrateNode().detachChildNamed(m.entityId);
                    } else {
                        FloatingCrate floatingCrate = new FloatingCrate(app,
                                app.getCrateNode(), m.balls, m.health, m.pos,
                                app.bulletAppState.getPhysicsSpace(),
                                m.entityId);
                    }
                } else if (msg instanceof ServerJoinMessage) {
                    Notify.debug("Client has now joined the game. Game starting!");
                    Settings.SENDING_NW_UPDATES = true;

                } else if (msg instanceof ServerGiveEntityMessage) {
                    Notify.debug("NEW SERVER_GIVE_ENTITY_MESSAGE!");
                    ServerGiveEntityMessage message = (ServerGiveEntityMessage) msg;

                    Notify.debug("creating the client's ship based on the info from server");
                    EntityModels model = EntityModels.valueOf(message.entityType);
                    EntityTeam team = EntityTeam.valueOf(message.entityTeam);
                    app.ships.createShip(
                            model.getValues().get(ShipConstants.MODEL_PATH.toString()).toString(),// shipmodel
                            message.position, // location
                            message.entityId, // name/id
                            model.getValues(), // model-specific values
                            team.getValues() // team-specific values
                            );

                    // called several times?
                    app.controlShip(); // we now have a ship to control!
                    // load the other values given from the message...
                    Spatial ship = app.ships.getShip(Settings.HOST_NAME);
                    ShipControl control = ship.getControl(ShipControl.class);
                    // control.setPhysicsRotation(message.rotation);

                    // control.getPhysicsRotationMatrix().//getPhysicsRotationMatrix()
                    Notify.debug("Received rotation:" + message.rotation);
                    // Notify.debug("Set Rotation: "+control.get);
                    // Notify.debug();
                    ship.setUserData(
                            Settings.ShipConstants.HITPOINTS.toString(),
                            message.hitpoints);

                } else if (msg instanceof CannonBallMessage) {
                    CannonBallMessage message = (CannonBallMessage) msg;
                    if (message.entityId.equals(Settings.HOST_NAME)) {
                        return null;
                    }
                    Spatial tmpship = app.ships.getShip(message.entityId);
                    CannonBall c = new CannonBall(app, 
                            tmpship.getControl(ShipControl.class), 
                            app.bulletAppState.getPhysicsSpace(), app.getRootNode());
                    c.shoot(message.position, message.velocity);
                } else if (msg instanceof DenyMessage) {
                    DenyMessage denyMsg = (DenyMessage) msg;
                    if (denyMsg.source instanceof CannonBallMessage) {
                        app.getRootNode().detachChildNamed(
                                ((CannonBallMessage) denyMsg.source).name);
                    } else if (denyMsg.source instanceof EntityMessage) {
                        if (!denyMsg.moveHere.equals(Vector3f.NAN)) {
                            ShipControl shipControl = app.ships.getShip(
                                    denyMsg.entityId).getControl(
                                    ShipControl.class);
                            shipControl.setPhysicsLocation(denyMsg.moveHere);
                            shipControl.setLinearVelocity(Vector3f.ZERO);
                        }
                    } else if (denyMsg.source instanceof CrateMessage) {
                        CrateMessage m = (CrateMessage) denyMsg.source;
                        if (!m.pickup) {
                            // Does not add back to prevent cheating.
                            app.getCrateNode().detachChildNamed(m.id + "");
                        } else {
                            Spatial dship = app.ships.getShip(m.ship);
                            float hp = dship.<Float>getUserData(ShipConstants.HITPOINTS.toString());
                            dship.setUserData(
                                    ShipConstants.HITPOINTS.toString(),
                                    hp -= m.health);
                            int ball = dship.<Integer>getUserData(ShipConstants.CANNON_BALLS.toString());
                            dship.setUserData(
                                    ShipConstants.CANNON_BALLS.toString(),
                                    ball -= m.balls);
                            app.getCrateNode().detachChildNamed(m.id + "");
                        }
                    }
                    Notify.debug("Denied action: " + denyMsg.reason);
                } else if (msg instanceof EntityMessage) {

                    EntityMessage message = (EntityMessage) msg;

                    if (message.entityId != null
                            && message.entityId.equals(Settings.HOST_NAME)) {
                        // Notify.debug("Received update from "+message.entityId+" about "+message.entityType+" - (not handled)");
                        return null; // we don't need to handle updates about
                        // our own entity. correct?
                    }

                    switch (EntityModels.valueOf(message.entityType)) {
                        case BATTLESHIP:
                        // Notify.debug("Received update about BATTLESHIP: "
                        // + message.entityId);
                        case FLAGSHIP:
                            Spatial ship = app.ships.getShip(message.entityId);
                            if (ship != null) { // does the ship exist?
                                // yes: update its values
                                // if(!((String)
                                // ship.<String>getUserData(ShipConstants.TEAM.toString())).contains(message.entityTeam)){
                                // //entity has changed team. possible? take
                                // action?
                                // }
                                if (((Float) ship.<Float>getUserData(ShipConstants.HITPOINTS.toString())) != message.hitpoints) {
                                    // change of hitpoint. update
                                    ship.setUserData(
                                            ShipConstants.HITPOINTS.toString(),
                                            message.hitpoints);
                                }
                                ShipControl shipControl = app.ships.getShip(
                                        message.entityId).getControl(
                                        ShipControl.class);
                                if (message.hitpoints <= 0) {
                                    shipControl.sink();
                                    return null;
                                }
                                shipControl.predict(message.position,
                                        message.velocity, message.rotation,
                                        message.angularVelocity,
                                        message.timeStamp);
                            } else {
                                // no, ship does not exist locally. add it. the
                                // server is always right.
                                Notify.debug("creating new ship based on server info for client "
                                        + message.entityId);

                                app.ships.createShip(
                                        EntityModels.valueOf(message.entityType).getValues().get(ShipConstants.MODEL_PATH.toString()).toString(),// shipmodel
                                        // //Settings.BATTLESHIP_MODEL,
                                        message.position, // location
                                        message.entityId, // name/id
                                        EntityModels.valueOf(message.entityType).getValues(), // model-specific
                                        // values
                                        EntityTeam.valueOf(message.entityTeam).getValues() // team-specific
                                        // values
                                        );
                            }

                            break;
                        default:
                            Notify.debug("Warning: Recieved message from "
                                    + "server with unexisting entity type: "
                                    + message.entityType);
                            break;
                    }

                } else if (msg instanceof EndGameMessage) {
                    EndGameMessage message = (EndGameMessage) msg;
                    Settings.SCENARIO_HAS_ENDED = true;
                    Notify.debug("GAME ENDED. Winning team: "
                            + message.winner);
                    Settings.winningTeam = EntityTeam.valueOf(message.winner);
                } // <-end of switch
                return null;
            }
        }// <--end of callable
                );
    }
}
